package biolabs.space;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents a position in a three-dimensional grid.
 * That is a vector from
 * {@latex.inline $\\mathbb{Z} \\times \\mathbb{Z} \\times \\mathbb{Z}$},
 * where {@latex.inline $\\mathbb{Z}$} is the set of integers.
 *
 * Instances are immutable.
 */
public class Discrete3DPosition
		extends DiscretePosition<Discrete3DPosition, Continuous3DPosition>
		implements Comparable<Discrete3DPosition> {

	public static final Discrete3DPosition ZERO = new Discrete3DPosition(0, 0, 0);
	public static final Discrete3DPosition ONE = new Discrete3DPosition(1, 1, 1);

	private final int x;
	private final int y;
	private final int z;

	public Discrete3DPosition(int x, int y, int z){
		this.x = x;
		this.y = y;
		this.z = z;
	}

	public final int getX(){
		return x;
	}

	public final int getY(){
		return y;
	}

	public final int getZ(){
		return z;
	}

	@Override
	public Discrete3DPosition one() {
		return ONE;
	}

	@Override
	public Discrete3DPosition zero() {
		return ZERO;
	}

	@Override
	public boolean isEqualTo(Discrete3DPosition p){
		return x==p.x && y==p.y && z==p.z;
	}

	@Override
	public int compareTo(Discrete3DPosition p) {
		if(x < p.x)
			return -1;
		if(x > p.x)
			return 1;
		if(y < p.y)
			return -1;
		if(y > p.y)
			return 1;
		if(z < p.z)
			return -1;
		if(z > p.z)
			return 1;
		return 0;
	}

	@Override
	@Deprecated
	public boolean equals(Object o){
		if(!(o instanceof Discrete3DPosition))
			return false;
		return isEqualTo((Discrete3DPosition)o);
	}

	@Override
	public int hashCode(){
		return x*31*31 + y*31 + z;
	}

	@Override
	public String toString(){
		return "("+x+','+y+','+z+')';
	}

	@Override
	public Integer innerProduct(Discrete3DPosition a) {
		return x*a.x + y*a.y + z*a.z;
	}

	@Override
	public double length() {
		return Math.sqrt(x*x + y*y + z*z);
	}

	@Override
	public Discrete3DPosition minus(Discrete3DPosition a) {
		return new Discrete3DPosition(x-a.x, y-a.y, z-a.z);
	}

	@Override
	public Discrete3DPosition plus(Discrete3DPosition a) {
		return new Discrete3DPosition(x+a.x, y+a.y, z+a.z);
	}

	@Override
	public Discrete3DPosition times(Integer k) {
		return new Discrete3DPosition(k*x, k*y, k*z);
	}

	@Override
	public Continuous3DPosition continuous() {
		return new Continuous3DPosition(x, y, z);
	}

	@Override
	public List<Discrete3DPosition> getNeighbors() {
		List<Discrete3DPosition> n = new ArrayList<Discrete3DPosition>(6);
		n.add(new Discrete3DPosition(x-1, y, z));
		n.add(new Discrete3DPosition(x+1, y, z));
		n.add(new Discrete3DPosition(x, y-1, z));
		n.add(new Discrete3DPosition(x, y+1, z));
		n.add(new Discrete3DPosition(x, y, z-1));
		n.add(new Discrete3DPosition(x, y, z+1));
		return n;
	}

	@Override
	public Discrete3DPosition mapMax(Discrete3DPosition a) {
		return new Discrete3DPosition(Math.max(x, a.x), Math.max(y, a.y), Math.max(z, a.z));
	}

	@Override
	public Discrete3DPosition mapMin(Discrete3DPosition a) {
		return new Discrete3DPosition(Math.min(x, a.x), Math.min(y, a.y), Math.min(z, a.z));
	}

	@Override
	public Integer getCoordinate(int i) {
		switch(i){
		case 0: return x;
		case 1: return y;
		case 2: return z;
		default: throw new IndexOutOfBoundsException(i+"is an invalid index into a 3-dimensional vector");
		}
	}

	@Override
	public int getDimension() {
		return 3;
	}

	@Override
	public Discrete3DPosition withChangedCoordinate(int idx, Integer value)
			throws IndexOutOfBoundsException {
		switch(idx){
		case 2: return new Discrete3DPosition(x, y, value);
		case 1: return new Discrete3DPosition(x, value, z);
		case 0: return new Discrete3DPosition(value, y, z);
		default: throw new IndexOutOfBoundsException(idx+"is an invalid index into a 3-dimensional vector");
		}
	}

	@Override
	public Discrete3DPosition newInstance(int[] coords) {
		if(coords.length != 3)
			throw new IllegalArgumentException("Expected int[3], but int["+coords.length+"] supplied");
		return new Discrete3DPosition(coords[0], coords[1], coords[2]);
	}

}
